﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Hvam.SharePoint.Search.Language.Parsers
{
    internal static class ParserExtensions
    {
        public static T Parse<T>(this Parser<T> parser, string input)
        {
            var result = parser(new ParserInput(input));

            if (result.IsSuccess())
            {
                var success = (Success<T>) result;
                return success.Result;
            }

            throw new ParseException();
        }

        public static Parser<IEnumerable<T>> Once<T>(this Parser<T> parser)
        {
            return parser.Select(Yield);
        }

        public static IEnumerable<T> Yield<T>(T r)
        {
            yield return r;
        }

        public static Parser<IEnumerable<T>> Concat<T>(this Parser<IEnumerable<T>> first, Parser<IEnumerable<T>> second)
        {
            return first.Then(f => second.Select(s => f.Concat(s)));
        }

        public static Parser<U> Then<T, U>(this Parser<T> first, Func<T, Parser<U>> second)
        {
            return i => first(i).IfSuccess(s => second(s.Result)(s.Input));
        }

        public static Parser<IEnumerable<T>> Many<T>(this Parser<T> parser)
        {
            return
                input =>
                    {
                        var list = new List<T>();

                        Result<T> r;
                        do
                        {
                            r = parser(input);
                            if (r is Success<T>)
                            {
                                var s = (Success<T>) r;
                                list.Add(s.Result);
                            }
                        } while (r is Success<T>);

                        return new Success<IEnumerable<T>>(list, input);
                    };
        }

        public static Parser<IEnumerable<T>> AtLeastOnce<T>(this Parser<T> parser)
        {
            return parser.Once().Then(t1 => parser.Many().Select(ts => t1.Concat(ts)));
        }

        public static Parser<string> Text(this Parser<IEnumerable<char>> characters)
        {
            return characters.Select(chs => new string(chs.ToArray()));
        }

        public static Parser<T> Or<T>(this Parser<T> first, Parser<T> second)
        {
            return
                input =>
                    {
                        var result = first(input);

                        if (result is Failure<T>)
                        {
                            return second(input);
                        }

                        return result;
                    };
        }

        public static Parser<T> Else<T>(this Parser<T> first, Parser<T> second)
        {
            return
                input =>
                {
                    var copy = new ParserInput(input);
                    var result = first(input);

                    if (result is Failure<T>)
                    {
                        return second(copy);
                    }

                    return result;
                };
        }
    }
}